﻿using System;
using System.Data;
using System.Configuration;
using System.Linq;
using System.Threading;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Xml.Linq;
using System.Collections.Generic;
using System.Collections;

namespace Microsoft.SqlCommunity.SQLRSS
{
    // <editorial>Ah, to have a real macro system.</editorial>

    /// <summary>
    /// This is a generic class that provides for multi-reader, single writer lock protected dictionaries.  It is used
    /// to maximize concurrency but ensure that conflicting multi-threaded access to the table is serialized when necessary.
    /// </summary>
    /// <typeparam name="TKey">What type of key will be stored in the dictonary to be protected</typeparam>
    /// <typeparam name="TValue">What type of value will be stored in the dictionary to be protected</typeparam>
    /// <typeparam name="TLoad">What type of object will be used to initialize the table</typeparam>
    public class ProtectedDictionary<TKey, TValue, TLoad>
    {
        // The internal dictionary (never exposed to callers) for associating keys with values.
        private Dictionary<TKey, TValue> _dict = new Dictionary<TKey, TValue>();

        // The internal (never exposed to callers) multiple reader/single writer lock which protects the table
        private ReaderWriterLockSlim _rwlock = new ReaderWriterLockSlim();

        // A spot to remember the caller-specified "I don't know" value.
        private TValue _nullValue;

        private ProtectedDictionary()
        {
        }

        // Constructor which allows the caller to specify an "I don't know" value.  
        public ProtectedDictionary(TValue nullValue)
        {
            this._nullValue = nullValue;
        }

        /// <summary>
        /// A thread-safe indexer that allow access to a particular key's value.
        /// </summary>
        /// <param name="key">What item to access</param>
        /// <returns>For getter returns what was found</returns>
        public TValue this[TKey key]
        {
            get { return LookupValue(key); }
            set { SetValue(key, value); }
        }
            
        /// <summary>
        /// A thread-safe way to read a key's value.  Raises an exception if the key is not found.
        /// </summary>
        /// <param name="key">What to look for</param>
        /// <returns>What was found</returns>
        public TValue LookupValue(TKey key)
        {
            try
            {

                _rwlock.EnterReadLock();
                return _dict[key];

            }
            finally
            {
                if (_rwlock.IsReadLockHeld) _rwlock.ExitReadLock();
            }
        }

        /// <summary>
        /// A thread-safe way to try and read a key's value. Simply returns false
        /// rather than raise an exception if a key is not found.  val is set to 
        /// the user specified null value if key is not found.
        /// </summary>
        /// <param name="key">What to look for</param>
        /// <param name="val">What was found</param>
        /// <returns>Whether something was found or not</returns>
        public bool TryLookupValue(TKey key, out TValue val)
        {
            try
            {
                _rwlock.EnterReadLock();
                if (_dict.ContainsKey(key))
                {
                    val = _dict[key];
                    return true;
                }
                else
                {
                    val = _nullValue;
                    return false;
                }
            }
            finally
            {
                if (_rwlock.IsReadLockHeld) _rwlock.ExitReadLock();
            }
        }

        /// <summary>
        /// A thread-safe way to change the value of a key.
        /// </summary>
        /// <param name="key"></param>
        /// <param name="newValue"></param>
        public void SetValue(TKey key, TValue newValue)
        {
            try
            {

                _rwlock.EnterWriteLock();
                _dict[key] = newValue;

            }
            finally
            {
                if (_rwlock.IsWriteLockHeld) _rwlock.ExitWriteLock();
            }
        }

        /// <summary>
        /// A thread-safe way to associate a new key with a value.
        /// </summary>
        /// <param name="key">What new key to include in the dictionary</param>
        /// <param name="newValue">What value to associate with the new key</param>
        public void Add(TKey key, TValue newValue)
        {
            try
            {

                _rwlock.EnterWriteLock();
                _dict.Add(key, newValue);

            }
            finally
            {
                if (_rwlock.IsWriteLockHeld) _rwlock.ExitWriteLock();
            }
        }

        /// <summary>
        /// A thread safe way to associate a key with a value whether or not the key is 
        /// already in the dictionary.
        /// </summary>
        /// <param name="key">What key to associate with the specified value</param>
        /// <param name="newValue">What value to associate with the specified key</param>
        public void AddOrSetValue(TKey key, TValue newValue)
        {
            try
            {

                TValue val;
                _rwlock.EnterWriteLock();
                if (_dict.TryGetValue(key, out val))
                {
                    _dict[key] = newValue;
                }
                else
                {
                    _dict.Add(key, newValue);
                }
            }
            finally
            {
                if (_rwlock.IsWriteLockHeld) _rwlock.ExitWriteLock();
            }
        }

        /// <summary>
        /// A thread-safe way to bulk load the dictionary with values without
        /// releasing locks until all values are loaded.  Given an enumerable collection of TLoad,
        /// use the caller-supplied loadKeyFunction and loadValueFunction to determine the key and value to add to the dictionary.
        /// This gives us maximum flexibility in the representation of the enumerable collection, and in the way 
        /// elements of the collection are decomposed to determine the key and value to be used, while ensuring type safety.
        /// Generics and lambda expressions, two great tastes that taste great together.
        /// </summary>
        /// <param name="loadKeyFunction">Given an object of type TLoad, give me a key for the dictionary</param>
        /// <param name="loadValueFunction">Given an object of type TLoad, give me the value to associate with the above key</param>
        /// <param name="loadCollection">An enumerable collection of TLoad</param>
        public void LoadValues(Func<TLoad, TKey> loadKeyFunction, Func<TLoad, TValue> loadValueFunction, 
            IEnumerable<TLoad> loadCollection)
        {
            try
            {
                _rwlock.EnterWriteLock();
                foreach (TLoad element in loadCollection)
                {
                    _dict.Add(loadKeyFunction(element), loadValueFunction(element));
                }
            }
            finally
            {
                if (_rwlock.IsWriteLockHeld) _rwlock.ExitWriteLock();
            }
        }

        /// <summary>
        /// Similar to LoadValues above, but this time a bit more complicated because there may already be key/value pairs in
        /// the table.  Here the loadKeyFunction operates the same way, but the mergeValueFunction is invoked with the TLoad object
        /// which represents a new value, a TValue object which represents the existing value, and it should return a value which will
        /// be the new (merged) associated value for the key.  If a key is no longer in the loadCollection, it is removed from the dictionary.
        /// All in a thread-safe way.
        /// </summary>
        /// <param name="loadKeyFunction">Extract a key from a TLoad object</param>
        /// <param name="mergeValueFunction">Merge a value from the TLoad object with any existing value and return the value to associate with the key</param>
        /// <param name="loadCollection">An enumerable collection of TLoad to do the merging on</param>
        public void MergeValues(Func<TLoad, TKey> loadKeyFunction, Func<TLoad, TValue, TValue> mergeValueFunction,
            IEnumerable<TLoad> loadCollection)
        {
            //The set of keys we've examined.  The value isn't used.  We use a dictonary to get O(1) lookup.
            Dictionary<TKey, byte> keysProcessed = new Dictionary<TKey, byte>();

            try
            {
                _rwlock.EnterWriteLock();
                foreach (TLoad element in loadCollection)
                {
                    TKey key = loadKeyFunction(element);
                    //Keep track of the keys in the loadCollection so we can remove those which aren't.
                    keysProcessed.Add(key, 0);
                    //Merge the old value with the new desired value.
                    if (_dict.ContainsKey(key))
                    {
                        _dict[key] = mergeValueFunction(element, _dict[key]);
                    }
                    else
                    {
                        _dict.Add(key, mergeValueFunction(element, _nullValue));
                    }
                }
                // Create a separate list of keys to remove.  You run into trouble when you
                // change a dictionary you are iterating over!
                List<TKey> keysToRemove = new List<TKey>();
                foreach (TKey tableKey in _dict.Keys)
                {
                    if (!keysProcessed.ContainsKey(tableKey))
                        keysToRemove.Add(tableKey);
                }

                // Remove the keys from the dictionary which aren't in the loadCollection anymore.
                foreach (TKey key in keysToRemove)
                {
                    _dict.Remove(key);
                }
            }
            finally
            {
                if (_rwlock.IsWriteLockHeld) _rwlock.ExitWriteLock();
            }
        }

        /// <summary>
        /// Given an enumerable collection of TLoad objects, extract the key from each TLoad object, and return an ordered list
        /// of values for each of the keys in a thread-safe way.
        /// </summary>
        /// <param name="lookupKeyFunction">How to get the key from a TLoad object</param>
        /// <param name="lookupCollection">An enumerable collection of TLoad objects</param>
        /// <returns>A list of values corresponding to the keys</returns>
        public List<TValue> LookupValues(Func<TLoad, TKey> lookupKeyFunction, IEnumerable<TLoad> lookupCollection)
        {
            try
            {
                List<TValue> results = new List<TValue>();
                TValue lookupResult;
                _rwlock.EnterReadLock();
                foreach (TLoad lookupElement in lookupCollection)
                {
                    if (_dict.TryGetValue(lookupKeyFunction(lookupElement), out lookupResult))
                        results.Add(lookupResult);
                }
                return results;
            }
            finally
            {
                if (_rwlock.IsReadLockHeld) _rwlock.ExitReadLock();
            }
        }


    }
}
